package com.zwh.se.collection.array;

import java.util.Arrays;

/**
 * @author yexi
 * 数组：一种用来将若干相同数据类型的数据存储起来的集合。其中，每一个数据称作一个元素，每个元素可以通过一个索引（下标）来访问它们。
 *
 * TIPS:数组并不是java中集合这个概念的一份子，但它是集合的重要底层实现，且笔者找不到更合适的位置来放他，所以才放在这里的。
 */
public class ArrayDemo {
    public static void main(String[] args) {
        defineArray();//定义数组
        System.out.println("----------------");
        ArrayCharacteristic();//数组的基本特点
        System.out.println("----------------");
        ArrayDefine();//数组初始化方式
        System.out.println("----------------");
        arrayTraversal();//遍历数组
        System.out.println("----------------");
        multiArray();//多维数组
    }


    /**
     * 多维数组的demo
     */
    private static void multiArray() {
        //这种叫做一维数组
        int[] arr = {1, 2, 3};

        //不规则二维数组
        int[][] array;//collection.array[i][j]==>collection.array[行][列]
        int[] arr1 = {1, 2, 3};
        int[] arr2 = {4, 5, 6, 7};
        int[] arr3 = {7, 8, 9, 10, 11};
        array = new int[][]{arr1, arr2, arr3};
        System.out.println("共有" + array.length + "行");//3

        //动态初始化
        //这里表示声明了一个三行的二维数组(表示能装3个一维数组),没有声明列(没有声明一维数组)
        //想完整初始化就写成 new int[3][6] 这样的
        //TIPS:直接声明了的话表示一维数组最多只能装声明的容量数据了,所以不提前声明有时反而更符合需求一些
        int[][] array2 = new int[3][];
        array2[0] = arr1;
        array2[1] = arr2;
        array2[2] = arr3;
        System.out.println(array2);//跟一维数组同理,只是输出了地址

        //字面量声明法(静态初始化)
        int[][] array3 = {
                {1, 2, 3,},
                {4, 5, 6, 7},
                {7, 8, 9, 10, 11, 12}
        };

        //遍历输出所有的元素(索引从 collection.array[0][0] 开始)
        System.out.println(array3[0][0]);
        for (int i = 0; i < array3.length; i++) {
            for (int j = 0; j < array3[i].length; j++) {
                System.out.print(array3[i][j] + " ");
            }
            System.out.println();
        }

        //定义二维数组的时候，必须要给定数组的长度

    }

    /**
     * 遍历数组的demo
     */
    private static void arrayTraversal() {
        int[] arr = {40, 30, 1, 2};
        System.out.println(arr.length);//长度为4

        // 访问数组的元素 ==> 通过下标/索引(index)==>index从0开始==>arr[index]
        int t1 = arr[0];//把数组中的元素提取出来给别的变量用
        System.out.println(t1);//40
        System.out.println(arr[1]);//30
        System.out.println(arr[2]);//1
        //数组的最大下标 == arr.length-1，同时也是这里的arr[3]
        System.out.println(arr[arr.length - 1]);//2

        // 挨个访问数组的元素 => 遍历数组元素
        for (int index = 0; index < arr.length; index++) {
            System.out.println(index);
            System.out.println(arr[index]);
        }

        //总结两种循环模式
        //i=0,i<length;循环次数:length次
        //i=1,i<=length;循环次数:length次

        //for-each 增强for循环:简单的写法,直接遍历出所有的元素,但是这种写法没有出现索引,用不了了。本质上是一种"语法糖"
        //for-each特别适合快速读取数组中的元素
        // 优点：代码简单
        // 缺点：单纯地增强for循环不能涉及跟索引相关的操作
        // TIPS:就数组而言,增强for和普通for不存在效率差异的问题,这点可以通过反编译的字节码文件看出来
        for (int item : arr) {
            System.out.println(item);
        }

        // 可以修改数组元素的值arr[index] = xx
        arr[0] = 400;
        System.out.println(arr[0]);
        System.out.println(t1);//测试用:t1已被上面的赋值,该值不会随着上面的更替而变更,还是40,需要重新赋值才会改变

        // 可能出现的异常：

        //ArrayIndexOutOfBoundsException超出边界：明明数组容量没这么长却想强行访问越界的索引
        //System.out.println(arr[4]);

        //NullPointerException空指针异常：数组此时存储着一个null值,不能强行调用涉及到包含数组元素的方法操作
        //arr = null;
        //System.out.println(arr[0]);
    }

    /**
     * 数组的初始化方式总共有三种：静态初始化、动态初始化、默认初始化。
     * <p>
     * 是一种并没有什么用的概念，甚至没什么存在的必要，就当看看就可以了
     */
    private static void ArrayDefine() {

        // 静态初始化：不直接指明要申请分配的数组元素数量，直接在定义数组的同时使用new或者不使用就为数组元素分配空间并赋值。
        // 举例：
        int[] arr1 = {12, 23, 45};
        int[] arr2 = new int[]{12, 23, 45};
        // 错误例子(会产生编译错误的例子)：
        // 反例1：int[] arr3 = new int[3]{12, 23, 45};
        // 反例2：int[] arr ;
        //       arr = {12,23,45};
        // 会有这些错误原因主要是因为编译器的词法分析器已经固定下来了，所以需要就着规则才可以正常使用

        // 动态初始化：数组定义与为数组元素分配空间并赋值的操作分开进行。
        // 举例：
        // 声明一个数组变量
        int[] array;
        // 再给数组分配堆内存
        array = new int[5];
        // 随后再想怎么赋值就怎么赋值
        array[0] = 12;
        array[1] = 23;
        array[2] = 45;

        // 默认初始化：数组定义的时候就直接new，且不直接指明要存储的元素，此时数组里面会存储有默认值
        // 原因：数组是引用类型，它的元素相当于类的实例变量，因此数组一经分配空间，其中的每个元素也被按照实例变量同样的方式被隐式初始化。
        // 举例：
        int[] arr = new int[5];
        System.out.println(arr.length);//5
        // 申请好了之后,里面是有默认值的(默认值根据数组类型而不同)
        System.out.println(Arrays.toString(arr));//[0, 0, 0, 0, 0]
    }

    /**
     * 通过测试demo来验证数组的基本特点
     */
    private static void ArrayCharacteristic() {

        // 1、数组一旦被创建，其长度就是确定的，它的大小就是不可以改变的。
        int[] arr = new int[4];
        // 模拟之后会学到的集合底层的写法
        int size = 0;
        arr[size++] = 12;
        arr[size++] = 56;
        arr[size++] = 98;
        arr[size++] = 47;
        // java.lang.ArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4
        // 数组索引越界异常：这是因为当创建时，就已经确定了数组只存储了4个元素(只有4个位置)，而却打算找第5个位置来存储，做不到，所以报错
        // arr[4] = 66;
        System.out.println(Arrays.toString(arr));

        // 2、同时从上面的代码可以看出：数组存在"索引"这个概念。且索引从0开始，到 数组.length-1 结束

        // 3、数组内的元素类型必须是相同类型，不允许出现混合类型。
        // arr[0] = 6.7; 直接产生编译错误
        arr[0] = '1'; // 特殊情况，详见自动类型转换条目，属于优化。因为char类型底层本质是数字

        // 4、数组类型可以是任何数据类型，包括基本类型和引用类型，且一旦初始化就有着默认值。
        System.out.println(Arrays.toString(new byte[0]));// (byte)0
        System.out.println(Arrays.toString(new short[1]));// (short)0
        System.out.println(Arrays.toString(new int[1]));// 0
        System.out.println(Arrays.toString(new long[1]));// 0L
        System.out.println(Arrays.toString(new float[1]));// 0.0f
        System.out.println(Arrays.toString(new double[1]));// 0.0d
        System.out.println(Arrays.toString(new boolean[1]));// false
        System.out.println(Arrays.toString(new char[1]));// '\u0000'(空字符串)
        System.out.println(Arrays.toString(new String[1]));// null(空引用)

        // 5、数组变量本身属于引用类型，数组也是对象。
        // PS:数组变量属于引用类型，数组也是对象，数组中的每个元素相当于该对象的成员变量。
        //    数组本身就是对象，Java中对象是在堆中的，因此数组无论保存原始类型还是其他对象类型，数组对象本身是在堆中存储的。

        // 6、插入数组中的数据是有序集合，此时有序并不是指代大小排序，而是指插入的顺序
    }

    /**
     * 定义一个数组并使用的demo
     */
    public static void defineArray() {
        // 声明一个数组变量,声明的数组是什么类型,数组中的元素都必须是什么类型
        // 有两种声明方式：
        int[] array;
        int array1[]; // 虽然数组声明有两种方式,但是第二种相对而言可读性比第一种稍差，所以一般不推荐使用

        // TIPS:内存分析：只是声明了数组定义，而没有为其赋值的情况下，该array变量存储于栈区
        // 此时打算对其直接使用会直接产生编译错误，这是因为如果数组只声明，没有后续操作，那么这个数组相当于没定义(可通过反编译字节码文件看出)
        //System.out.println(array);

        // null表示的是array哪儿也不指向,也即在堆内存中没有对应的空间,不能引用使用（也就是说null指的是"空引用(空指针)"）
        // 该代码真正的意思：为该数组存储一个可被标识为null引用的值，之后若打算使用时就可以取出里面存储着的这个值来进行判断是否可操作
        array = null;
        // 因此注意：赋值为null和什么都没有赋值，是不一样的
        System.out.println(array);
        //System.out.println(array.length);当然如果打算使用时就会出现空指针异常,指的就是这个array数组并没有可用元素的意思
        // java.lang.NullPointerException 空指针异常

        // 为array变量在堆中申请对应大小的内存空间(此处存储4个int类型,可认为申请了4*4=16Byte的空间，并将该内存空间的访问地址返回给array变量存储)
        // 实际上在编译的时候，声明和创建会由于编译器的词法分析器而自动合并为一句话(可通过反编译字节码文件看出)
        array = new int[]{1, 2, 3, 4};
        // 注意的是array变量本身依旧是存在于栈区，但是它里面已经存储了一个指向堆区空间的地址了。
        // 此处输出的已经是地址了
        // 一般而言，此返回的地址是数组首个元素的地址(因为只要有一个首地址，其余元素就可以通过顺序IO直接找到了)
        System.out.println(array);//[I@7c30a502 数组+int类型+@+地址
        //访问数组的长度(容量)
        System.out.println(array.length);//4

        // 可以在声明的同时就直接初始化,合二为一的写法
        int[] array2 = new int[]{10, 20, 30, 40, 50};
        System.out.println(array2.length);//5
        //TIPS:在实际开发中,在已知数组的值时,可以选择更方便的---字面量声明法
        //注意:字面量声明法必须写到同一个 ; 的前面去
        int[] array3 = {100, 200, 300, 400, 500};
        System.out.println(array3.length);//5

        // array重定向到其他内存
        // 内存分析：在之前array存储了一个[I@7c30a502的指向地址，而当使用了new之后，就在堆区开辟了一块新的内存空间，并将首地址返回，
        //         新的首地址就是[I@49e4cb85，然后直接覆盖了[I@7c30a502。
        array = new int[]{1, 2, 3, 4};//覆盖之前的指向地址
        System.out.println(array);//[I@49e4cb85

    }

    /*
        【1】数组，集合都是对多个数据进行存储操作的，简称为容器。
        PS:这里的存储指的是内存层面的存储，而不是持久化存储（.txt,.avi,.jpg,数据库）。

        【2】数组的缺点：
        （1）数组一旦指定了长度，那么长度就被确定了，不可以更改。
        （2）删除，增加元素  效率低。
        （3）数组中实际元素的数量是没有办法获取的，没有提供对应的方法或者属性来获取
        （4）数组存储：有序，可重复 ，对于无序的，不可重复的数组不能满足要求。
        基于这些缺点，因此才引入了集合这个存储数据的结构概念
     */
}
